package account

import (
	"context"
	"gf_blog_admin/internal/consts"
	"gf_blog_admin/internal/dao"
	"gf_blog_admin/internal/model"
	"gf_blog_admin/internal/model/entity"
	"gf_blog_admin/internal/service"
	"gf_blog_admin/utility/casbinPolicy"
	"gf_blog_admin/utility/crypto"
	"github.com/gogf/gf/v2/container/gset"
	"github.com/gogf/gf/v2/database/gdb"
	"github.com/gogf/gf/v2/errors/gerror"
	"github.com/gogf/gf/v2/os/gtime"
)

type sAccount struct{}

func init() {
	service.RegisterAccount(NewAccount())
}

func NewAccount() *sAccount {
	return &sAccount{}
}

func (s *sAccount) Info(ctx context.Context) (out *model.AccountInfoItem, err error) {
	user, err := s.getUser(ctx)
	if err != nil {
		return nil, err
	}

	out = &model.AccountInfoItem{
		Avatar:   user.Avatar,
		Username: user.Username,
		Nickname: user.Nickname,
		Mobile:   user.Mobile,
		Email:    user.Email,
		Sex:      user.Sex,
	}

	res, err := dao.BlSysUserInfo.Ctx(ctx).
		Where(dao.BlSysUserInfo.Columns().UserId, user.Id).
		One()
	if err != nil {
		return nil, err
	}
	if !res.IsEmpty() {
		err = res.Struct(out)
	}
	return
}

func (s *sAccount) OptionLogs(ctx context.Context, in *model.AccountOpLogsInput) (out []*model.AccountOpLogsItem, total int, err error) {
	var (
		uid = ctx.Value(consts.CtxUId).(uint64)
		cls = dao.BlSysOptionLog.Columns()
	)
	out = make([]*model.AccountOpLogsItem, 0)
	err = dao.BlSysOptionLog.Ctx(ctx).
		Where(cls.UserId, uid).
		WhereGTE(cls.CreatedAt, gtime.Now().AddDate(0, 0, -6)).
		Fields(cls.IpAddress, cls.ModuleType, cls.ModuleId, cls.LogAction, cls.LogRemark, cls.CreatedAt).
		Page(in.Page, in.PageSize).
		OrderDesc(cls.Id).
		ScanAndCount(&out, &total, false)
	return
}

func (s *sAccount) LoginLogs(ctx context.Context, in *model.AccountLoginLogsInput) (out []*model.AccountLoginLogsItem, total int, err error) {
	var (
		uid = ctx.Value(consts.CtxUId).(uint64)
		cls = dao.BlSysLoginLog.Columns()
	)
	out = make([]*model.AccountLoginLogsItem, 0)
	err = dao.BlSysLoginLog.Ctx(ctx).
		Where(cls.UserId, uid).
		WhereGTE(cls.CreatedAt, gtime.Now().AddDate(0, 0, -6)).
		Fields(cls.IpAddress, cls.DeviceInfo, cls.LogType, cls.Platform, cls.Browser, cls.LogRemark, cls.CreatedAt).
		Page(in.Page, in.PageSize).
		OrderDesc(cls.Id).
		ScanAndCount(&out, &total, false)
	return
}

func (s *sAccount) UpAvatar(ctx context.Context, path string) error {
	user, err := s.getUser(ctx)
	if err != nil {
		return err
	}

	return dao.BlSysUser.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		cls := dao.BlSysUser.Columns()
		_, err = dao.BlSysUser.Ctx(ctx).
			Where(cls.Id, user.Id).
			Where(cls.UserStatus, 1).
			Data(map[string]string{cls.Avatar: path}).
			Update()
		return err
	})
}

func (s *sAccount) Update(ctx context.Context, in *model.AccountInfoUpdateInput) error {
	user, err := s.getUser(ctx)
	if err != nil {
		return err
	}

	return dao.BlSysUser.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		cls := dao.BlSysUser.Columns()
		_, err = dao.BlSysUser.Ctx(ctx).
			Where(cls.Id, user.Id).
			Where(cls.UserStatus, 1).
			Data(map[string]interface{}{
				cls.Nickname: in.Nickname,
				cls.Sex:      in.Sex,
			}).
			OmitEmptyData().Update()
		if err != nil {
			return err
		}

		infoCls := dao.BlSysUserInfo.Columns()
		_, err = dao.BlSysUserInfo.Ctx(ctx).
			Data(map[string]interface{}{
				infoCls.UserId:   user.Id,
				infoCls.Birthday: in.Birthday,
				infoCls.Intro:    in.Intro,
				infoCls.Country:  in.Country,
				infoCls.Province: in.Province,
				infoCls.City:     in.City,
				infoCls.Address:  in.Address,
			}).OmitEmptyData().Save()
		return err
	})
}

func (s *sAccount) ResetPwd(ctx context.Context, in *model.AccountResetPwdInput) error {
	user, err := s.getUser(ctx)
	if err != nil {
		return err
	}
	if !crypto.PasswordVerify(in.OldPwd, user.PasswordHash) {
		return gerror.New("输入的密码不正确")
	}

	return dao.BlSysUser.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
		cls := dao.BlSysUser.Columns()
		_, err = dao.BlSysUser.Ctx(ctx).
			Where(cls.Id, user.Id).
			Where(cls.UserStatus, 1).
			Data(map[string]string{
				cls.PasswordHash: crypto.PasswordHash(in.NewPwd, 10),
			}).Update()
		return err
	})
}

func (s *sAccount) SysMenus(ctx context.Context) (out *model.SysMenuOutput, err error) {
	var (
		user  = ctx.Value(consts.CtxUser).(*model.JwtClaims)
		mCls  = dao.BlSysMenu.Columns()
		menus []*model.MenuItem
	)
	err = dao.BlSysMenu.Ctx(ctx).
		WhereIn(mCls.MenuGenre, []uint{1, 2, 3, 4}).
		Where(mCls.MenuStatus, 1).
		Fields(
			mCls.Id, mCls.MenuName, mCls.DisplayName, mCls.MenuPath, mCls.Component,
			mCls.MenuIcon, mCls.MenuGenre, mCls.ParentId, mCls.MenuHidden,
		).
		Scan(&menus)
	if err != nil {
		return nil, err
	}
	out = &model.SysMenuOutput{
		DashboardGrid: []string{
			"welcome", "ver", "time", "progress", "echarts", "about",
		},
	}
	if user.IsSuper || user.Role == consts.SuperRole {
		out.Menus = s.formMenuTree(0, menus)
		permissions := make(chan []string, 1)
		go func() {
			defer close(permissions)
			var p []string
			for _, m := range menus {
				p = append(p, m.MenuName)
			}
			permissions <- p
		}()
		out.Permissions = <-permissions
		return
	}

	out.Permissions = s.getUserRules(user.Username)
	var (
		strSet   = gset.NewStrSetFrom(out.Permissions)
		newMenus = make(chan []*model.MenuItem, 1)
	)
	go func() {
		defer close(newMenus)
		var m []*model.MenuItem
		for _, menu := range menus {
			if menu.MenuGenre != 2 && strSet.Contains(menu.MenuName) {
				m = append(m, menu)
			}
		}
		newMenus <- m
	}()
	out.Menus = s.formMenuTree(0, <-newMenus)
	return
}

// 获取当前用户
func (s *sAccount) getUser(ctx context.Context) (user *entity.BlSysUser, err error) {
	var (
		uid = ctx.Value(consts.CtxUId).(uint64)
		cls = dao.BlSysUser.Columns()
	)

	err = dao.BlSysUser.Ctx(ctx).
		Where(cls.Id, uid).
		Where(cls.UserStatus, 1).
		Scan(&user)
	if err != nil {
		return nil, err
	}
	if user == nil {
		return nil, gerror.New("用户不存在")
	}
	return
}

// 获取用户权限
func (s *sAccount) getUserRules(username string) []string {
	rules, err := casbinPolicy.Enforcer.GetPermissionsForUser(username)
	if err != nil {
		return nil
	}
	list := make([]string, 0)
	for _, v := range rules {
		if v[1] != "" {
			list = append(list, v[1])
		}
	}
	return list
}

// 菜单树形结构
func (s *sAccount) formMenuTree(pid uint64, nodes []*model.MenuItem) []*model.SysMenuTreeItem {
	var (
		menusTypes = map[uint]string{0: "none", 1: "menu", 2: "button", 3: "iframe", 4: "link"}
		tree       = make([]*model.SysMenuTreeItem, 0)
	)
	for _, v := range nodes {
		if v.MenuGenre == 2 {
			continue
		}
		if v.ParentId == pid {
			child := s.formMenuTree(v.Id, nodes)
			menusType := menusTypes[v.MenuGenre]
			if menusType == "" {
				menusType = "none"
			}
			item := &model.SysMenuTreeItem{
				SysMenuItem: &model.SysMenuItem{
					Id:       v.Id,
					Mame:     v.MenuName,
					Path:     v.MenuPath,
					ParentId: v.ParentId,
					Meta: &model.SysMenuMetaItem{
						Title:  v.DisplayName,
						Icon:   v.MenuIcon,
						Hidden: v.MenuHidden == 1,
						Type:   menusType,
					},
					Component: v.Component,
				},
				Children: child,
			}
			tree = append(tree, item)
		}
	}
	return tree
}
